package com.frank.subject.domain.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.frank.common.enums.IsDeletedFlagEnum;
import com.frank.common.utils.LoginUtil;
import com.frank.config.redis.RedisUtil;
import com.frank.common.entity.PageInfo;
import com.frank.subject.domain.convert.SubjectInfoBoConverter;
import com.frank.subject.domain.entity.SubjectInfoBo;
import com.frank.subject.domain.entity.SubjectOptionBo;
import com.frank.subject.domain.handler.subject.SubjectTypeHandler;
import com.frank.subject.domain.handler.subject.SubjectTypeHandlerFactory;
import com.frank.subject.domain.service.SubjectDomainService;
import com.frank.subject.domain.service.SubjectLikedDomainService;
import com.frank.subject.infra.basic.entity.SubjectInfo;
import com.frank.subject.infra.basic.entity.SubjectLabel;
import com.frank.subject.infra.basic.entity.SubjectMapping;
import com.frank.subject.infra.basic.service.SubjectInfoService;
import com.frank.subject.infra.basic.service.SubjectLabelService;
import com.frank.subject.infra.basic.service.SubjectMappingService;
import com.frank.subject.infra.es.subject.entity.SubjectInfoEs;
import com.frank.subject.infra.es.subject.service.SubjectInfoEsService;
import com.frank.subject.infra.rpc.UserRpc;
import com.frank.subject.infra.rpc.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Frank Zou
 * @version v0.0.1
 * @date 2024/1/25 1:51 PM
 * @description
 */
@Slf4j
@Service
public class SubjectDomainServiceImpl implements SubjectDomainService {

    private final SubjectInfoService subjectInfoService;

    private final SubjectTypeHandlerFactory subjectTypeHandlerFactory;

    private final SubjectMappingService subjectMappingService;

    private final SubjectLabelService subjectLabelService;

    private final SubjectInfoEsService subjectInfoEsService;

    private final RedisUtil<String, Object> redisUtil;

    private final UserRpc userRpc;

    private final SubjectLikedDomainService subjectLikedDomainService;

    public static final String REDIS_RANK_KEY = "subject.rank.key";

    public SubjectDomainServiceImpl(SubjectInfoService subjectInfoService,
                                    SubjectTypeHandlerFactory subjectTypeHandlerFactory,
                                    SubjectMappingService subjectMappingService,
                                    SubjectLabelService subjectLabelService,
                                    SubjectInfoEsService subjectInfoEsService,
                                    RedisUtil<String, Object> redisUtil,
                                    UserRpc userRpc,
                                    SubjectLikedDomainService subjectLikedDomainService) {
        this.subjectInfoService = subjectInfoService;
        this.subjectTypeHandlerFactory = subjectTypeHandlerFactory;
        this.subjectMappingService = subjectMappingService;
        this.subjectLabelService = subjectLabelService;
        this.subjectInfoEsService = subjectInfoEsService;
        this.redisUtil = redisUtil;
        this.userRpc = userRpc;
        this.subjectLikedDomainService = subjectLikedDomainService;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(SubjectInfoBo subjectInfoBO) {
        SubjectInfo subjectInfo = SubjectInfoBoConverter.INSTANCE.convertBoToInfo(subjectInfoBO);
        subjectInfo.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        subjectInfoService.save(subjectInfo);
        // 将数据库返回的 SubjectInfoEs id 设置到 Bo 中
        subjectInfoBO.setId(subjectInfo.getId());
        // 使用工厂加策略模式添加题目的答案选项
        SubjectTypeHandler handler = subjectTypeHandlerFactory.getHandler(subjectInfo.getSubjectType());
        handler.add(subjectInfoBO);
        List<SubjectMapping> list = new LinkedList<>();
        // 添加题目和标签，类型的映射关系
        subjectInfoBO.getCategoryIdList().forEach(categoryId -> {
            for (Integer labelId : subjectInfoBO.getLabelIdList()) {
                SubjectMapping subjectMapping = new SubjectMapping();
                subjectMapping.setSubjectId(subjectInfoBO.getId());
                subjectMapping.setLabelId(Long.valueOf(labelId));
                subjectMapping.setCategoryId(Long.valueOf(categoryId));
                subjectMapping.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
                list.add(subjectMapping);
            }
        });
        subjectMappingService.saveBatch(list);
        // 同步到 ES
        SubjectInfoEs subjectInfoEs = SubjectInfoBoConverter.INSTANCE.convertBoToEs(subjectInfoBO);
        subjectInfoEsService.insertSubject(subjectInfoEs);
        // redis 记录排行榜
        String loginId = LoginUtil.getLoginId();
        redisUtil.addScore(REDIS_RANK_KEY, loginId, 1);
    }


    @Override
    public Page<SubjectInfoBo> getSubjectPage(SubjectInfoBo subjectInfoBO, PageInfo pageInfo) {
        SubjectInfoEs subjectInfoEs = SubjectInfoBoConverter.INSTANCE.convertBoToEs(subjectInfoBO);
        Page<SubjectInfoEs> subjectInfoEsPage = subjectInfoEsService.page(subjectInfoEs, pageInfo);
        Page<SubjectInfoBo> subjectInfoBoPage = SubjectInfoBoConverter.INSTANCE.convertEsPageToBoPage(subjectInfoEsPage);
        String loginId = LoginUtil.getLoginId();
        subjectInfoBoPage.getRecords().forEach(bo -> {
            if (StringUtils.isNotBlank(loginId)) {
                bo.setLiked(subjectLikedDomainService.isLiked(bo.getId().toString(), LoginUtil.getLoginId()));
            }
            bo.setLikedCount(subjectLikedDomainService.getLikedCount(bo.getId().toString()));
        });
        return subjectInfoBoPage;
    }

    @Override
    public SubjectInfoBo querySubjectInfo(SubjectInfoBo subjectInfoBO) {
        SubjectInfo subjectInfo = subjectInfoService.getById(subjectInfoBO.getId());
        if (subjectInfo == null) {
            return new SubjectInfoBo();
        }
        SubjectTypeHandler handler = subjectTypeHandlerFactory.getHandler(subjectInfo.getSubjectType());
        SubjectOptionBo subjectOptionBo = handler.query(subjectInfo.getId());
        SubjectInfoBo result = SubjectInfoBoConverter.INSTANCE.convertEntityAndOptionToBo(subjectOptionBo, subjectInfo);
        SubjectMapping subjectMapping = new SubjectMapping();
        subjectMapping.setSubjectId(result.getId());
        subjectMapping.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        List<Long> labelIds = subjectMappingService.queryLabelId(subjectMapping);
        if (!labelIds.isEmpty()) {
            List<String> labelNames = subjectLabelService.queryBatchLabelByIds(labelIds).stream().map(SubjectLabel::getLabelName).collect(Collectors.toList());
            result.setLabelNames(labelNames);
        }
        // 获取该题目的点赞数量以及点赞状态
        result.setLiked(subjectLikedDomainService.isLiked(result.getId().toString(), LoginUtil.getLoginId()));
        result.setLikedCount(subjectLikedDomainService.getLikedCount(result.getId().toString()));
        assembleSubjectInfoCursor(subjectInfoBO, result);
        return result;
    }

    /**
     * 设置上一题和下一题的题目 ID
     *
     * @param params 查询条件
     * @param result 查询结果
     */
    private void assembleSubjectInfoCursor(SubjectInfoBo params, SubjectInfoBo result) {
        Long labelId = params.getLabelId();
        Long categoryId = params.getCategoryId();
        Long subjectId = result.getId();
        if (Objects.isNull(labelId) || Objects.isNull(categoryId) || Objects.isNull(subjectId)) {
            return;
        }
        Long nextSubjectId = subjectInfoService.querySubjectIdByCursor(subjectId, categoryId, labelId, 1);
        result.setNextSubjectId(nextSubjectId);
        Long lastSubjectId = subjectInfoService.querySubjectIdByCursor(subjectId, categoryId, labelId, -1);
        result.setLastSubjectId(lastSubjectId);
    }

    @Override
    public List<SubjectInfoBo> getContributeList() {
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisUtil.rankWithScore(REDIS_RANK_KEY, 0, 5);
        if (CollectionUtils.isEmpty(typedTuples)) {
            return Collections.emptyList();
        }
        return typedTuples.stream().map(typedTuple -> {
            SubjectInfoBo subjectInfoBo = new SubjectInfoBo();
            subjectInfoBo.setCreateUserId(Objects.requireNonNull(typedTuple.getValue()).toString());
            subjectInfoBo.setSubjectCount(Objects.requireNonNull(typedTuple.getScore()).intValue());
            UserInfo userInfo = userRpc.getUserInfo(Objects.requireNonNull(typedTuple.getValue()).toString());
            subjectInfoBo.setCreateUser(userInfo.getUsername());
            subjectInfoBo.setCreateUserAvatar(userInfo.getNickname());
            return subjectInfoBo;
        }).collect(Collectors.toList());
    }


    @Override
    public boolean syncEs() {
        log.info("开始准备 Subject 同步到 Elasticsearch 任务...");
        List<SubjectInfo> subjectList = subjectInfoService.list();
        if (CollectionUtils.isEmpty(subjectList)) {
            log.info("未找到符合要求的记录，同步结束...");
            return true;
        }
        List<SubjectInfoBo> insertSubjectList = new ArrayList<>();

        List<SubjectInfoBo> subjectInfoBoList = SubjectInfoBoConverter.INSTANCE.convertEntityListToBoList(subjectList);
        subjectInfoBoList.forEach(subjectInfoBo -> {
            LambdaQueryWrapper<SubjectMapping> mappingQuery = Wrappers.lambdaQuery();
            mappingQuery.eq(SubjectMapping::getSubjectId, subjectInfoBo.getId())
                    .eq(SubjectMapping::getIsDeleted, IsDeletedFlagEnum.UN_DELETED.getCode());
            List<SubjectMapping> mappingList = subjectMappingService.list(mappingQuery);
            if (!CollectionUtils.isEmpty(mappingList)) {
                List<Integer> labelIdList = new ArrayList<>();
                List<Integer> categoryIdList = new ArrayList<>();
                mappingList.forEach(mapping -> {
                    if (!categoryIdList.contains(mapping.getCategoryId().intValue())) {
                        categoryIdList.add(mapping.getCategoryId().intValue());
                    }
                    if (!labelIdList.contains(mapping.getLabelId().intValue())) {
                        labelIdList.add(mapping.getLabelId().intValue());
                    }
                });
                subjectInfoBo.setLabelIdList(labelIdList);
                subjectInfoBo.setCategoryIdList(categoryIdList);
                insertSubjectList.add(subjectInfoBo);
            }
        });
        List<SubjectInfoEs> subjectInfoEsList = SubjectInfoBoConverter.INSTANCE.convertBoListToEsList(insertSubjectList);
        subjectInfoEsService.insertBatchSubject(subjectInfoEsList);
        return true;
    }
}
